<html><head><meta http-equiv=Content-Type content='text/html; charset=UTF-8'></head>
NEW

0 GOTO 1000
1  REM * CFFA Utility by David A. Lyons
900 :
900 REM * Memory use:
905 REM *   $0300 = EEPROM writing, EPROM/EEPROM detection
906 REM *   $03C0 = COUT filter,
910 REM *   $0800 = scratch space
920 REM *   $1000..1FFF = 4K firmware image, loaded from disk
930 REM *   $2000 = this BASIC code
999 :
1000 REM * Relocate us to $2000
1002 IF PEEK (104) < > 32 THEN POKE 103,1: POKE 104,32: POKE 32 * 256,0: PRINT CHR$ (4);"RUN CFFA.UTIL"
1010 VN$ = "2.0":DT$ = "3-May-2008"
1020 GOSUB 50000
1030 GOSUB 20000
1040 CC% = 1 : SLOT = CF%(CC%)
1090 M = 1
1100 REM * Main Menu
1105 M9 = 5
1110 M$(1) = "Settings"
1120 M$(2) = "Show CFFA Details"
1140 M$(3) = "Upgrade EEPROM"
1150 M$(4) = "Change Slot"

   REM If there is no CFFA card present, all you can do is Quit.
1160 IF SLOT=0 THEN M9=1
1190 M$(M9) = "Quit"
1200 IF SLOT = 0 THEN CC$ = "No CFFA card found in any slot."
1210 IF SLOT < > 0 THEN CC$ = "Using CFFA card in slot " + STR$ (SLOT) + "."

1300 HOME : PRINT CC$
1305 PRINT : PRINT 
1310 GOSUB 40000
1315 IF M=M9 THEN 19000
1320 HOME : ON M GOSUB 12000,11000,30000,14000,19000
1390 GOTO 1100

9999 :
10999 :
11000 REM * CFFA details
11010 IF SLOT = 0 THEN C$ = "No CFFA card found": GOSUB 60600: RETURN 
11015 PRINT 
11020 PRINT "Details for CFFA in slot ";SLOT;":"
11030 BASE = C000 + 256 * SLOT

   REM IF $CnFA vers is 255 then it's 2.0 or greater -> get the "real" version from $C822.
11040 V = PEEK (BASE + 250) : IF V=255 THEN POKE MSLOT,192+SLOT: Z=PEEK(CFFF): Z=PEEK(BASE): V=PEEK(C800+34)
11050 PRINT : PRINT "Firmware version "; INT (V / 16);".";V - 16 * INT (V / 16)

   REM If $CnF5 is FF, then the real GS/OS driver level is at $C823
REM 11060 L=PEEK(BASE+245): IF L=255 THEN POKE MSLOT,192+SLOT: Z=PEEK(CFFF): Z=PEEK(BASE): L=PEEK(C800+35)
REM 11070 PRINT "GS/OS Driver level "; L
   REM  300: jsr $Cnxx to SmartPort entry
   REM  303: 0 = status
   REM  304: $310 = parameter list
   REM  306: sta 00
   REM  308: php
   REM  309: pla
   REM  30A: and #1
   REM  30B: sta 01
   REM  30C: rts
11100 S = 768: POKE S,32: POKE S + 1, PEEK (BASE + 255) + 3: POKE S + 2,192 + SLOT
11110 POKE S + 3,0: REM * SP status
11120 POKE S + 4,16: POKE S + 5,3
11130 POKE S + 6,133: POKE S + 7,0: POKE S + 8,8: POKE S + 9,104: POKE S + 10,41: POKE S + 11,1: POKE S + 12,133: POKE S + 13,1: POKE S + 14,96
11140 POKE S + 16,3: POKE S + 17,0: POKE S + 18,0: POKE S + 19,8: POKE S + 20, ASC ("I")
11150 CALL S
11160 PRINT : IF PEEK(0) THEN 11300 : REM error
   REM display results of the first Identify call
11170 PRINT: PRINT "Dev0 Identify results:"
11200 FOR X = 2102 TO 2141: PRINT CHR$ ( PEEK (X));: NEXT : PRINT 
11210 FOR X = 2068 TO 2087: PRINT CHR$ ( PEEK (X));: NEXT : PRINT 
11220 FOR X = 2094 TO 2101: PRINT CHR$ ( PEEK (X));: NEXT 

   REM try Dev1 -- status code is one less than "I"
11300 POKE S+20, ASC("H"): CALL S
11310 IF PEEK(0) THEN  11980 : REM error
11320 ?:?:?"Dev1 Identify results:"
11330 FOR X = 2102 TO 2141: PRINT CHR$ ( PEEK (X));: NEXT : PRINT 
11340 FOR X = 2068 TO 2087: PRINT CHR$ ( PEEK (X));: NEXT : PRINT 
11350 FOR X = 2094 TO 2101: PRINT CHR$ ( PEEK (X));: NEXT 

11980 GOSUB 61500
11990 RETURN 
11999 :

    REM ============================================================================================
    REM C800 Max32MBPartitionsDev0: range 0 to 13
    REM C801 Max32MBPartitionsDev1: range 0 to 13
    REM C802 DefaultBootDevice: range 0 to 1
    REM C803 DefaultBootPartition: range 1 to 13
    REM C809 MenuSnagMask: ($DF) allow "m" or "M" to activate the boot-time menu
    REM C80A MenuSnagKey: ('M'+$80)
    REM C80B BootTimeDelayTenths: number of tenths of a second to wait on boot before kbd check
    REM C80C BusResetSeconds: number of seconds to wait for ready after bus reset
    REM C80D CheckDeviceTenths: (100) number of tenths of a second to wait for device to be ready
    REM C80E ConfigOptionBits: set bit $80 to skip bus reset
    REM C80F BlockOffsetDev0: range 0 to 2^24 blocks (8 GB)
    REM C812 BlockOffsetDev1: range 0 to 2^24 blocks (8 GB)
    REM
    REM C820_Signature: 'CF'
    REM C822_VersionByte:
    REM C824_FeatureBits - $80=65C02, $40=EEPROM, $20=WriteProtect, $10=RawBlocks
    REM ============================================================================================

12000 REM * Settings
12010 BASE=C000+256*SLOT: V=PEEK(BASE+250): IF V=255 THEN POKE MSLOT,192+SLOT: Z=PEEK(CFFF): Z=PEEK(BASE): V=PEEK(C800+34)
12020 IF V < 32 THEN C$="Requires firmware 2.0 or later.": GOSUB 60600: RETURN

12090 M=1
   REM set up V() and V$() with current settings
12100 POKE MSLOT,192+SLOT: Z=PEEK(CFFF): Z=PEEK(BASE)
12110 V(1)=PEEK(C800+0): MN(1)=0: MX(1)=13: OFF(1)=0
12115 V(2)=PEEK(C800+1): MN(2)=0: MX(2)=13: OFF(2)=1
12120 V(3)=PEEK(C800+2): MN(3)=0: MX(3)=1: OFF(3)=2
12125 V(4)=PEEK(C800+3): MN(4)=1: MX(4)=13: OFF(4)=3
12130 V(5)=PEEK(C800+10): KM=PEEK(C800+9): OFF(5)=10: V$(5)="none": IF V(5)>127 THEN V(5)=V(5)-128
12131 IF V(5)>31 THEN V$(5)=CHR$(V(5))
12132 IF V(5)<32 THEN V$(5)="^"+CHR$(V(5)+64)
12133 IF V(5)=0 AND KM=0 THEN V$(5)="always"
12135 V(6)=PEEK(C800+11): V$(6)=STR$(INT(V(6)/10))+"."+STR$(V(6)-10*INT(V(6)/10)): MN(6)=0: MX(6)=25.5: OFF(6)=11
12140 V(7)=PEEK(C800+12): MN(7)=0: MX(7)=255: OFF(7)=12
12145 V(8)=PEEK(C800+13): V$(8)=STR$(INT(V(8)/10))+"."+STR$(V(8)-10*INT(V(8)/10)): MN(8)=0: MX(8)=25.5: OFF(8)=13
12150 V(9)=PEEK(C800+15) + 256*PEEK(C800+16) + 65536*PEEK(C800+17): V$(9)=STR$(V(9)): MN(9)=0: MX(9)=16777215: OFF(9)=15
12155 V(10)=PEEK(C800+18) + 256*PEEK(C800+19) + 65536*PEEK(C800+20): V$(10)=STR$(V(10)): MN(10)=0: MX(10)=16777215: OFF(10)=18

12200 M9=1: M$(M9)="Partitions Dev0 (" + STR$(V(1)) + ")"
12210 M9=M9+1: M$(M9)="Partitions Dev1 (" + STR$(V(2)) + ")"
12220 M9=M9+1: M$(M9)="Boot Dev (" + STR$(V(3)) + ")"
12230 M9=M9+1: M$(M9)="Boot Partition (" + STR$(V(4)) + ")"
12240 M9=M9+1: M$(M9)="Menu key (" + V$(5) + ")"
12250 M9=M9+1: M$(M9)="Menu check delay (" + V$(6) + ")"
12260 M9=M9+1: M$(M9)="Bus reset timeout (" + STR$(V(7)) + ")"
12270 M9=M9+1: M$(M9)="Device timeout (" + V$(8) + ")"
12280 M9=M9+1: M$(M9)="Block offset Dev0 (" + V$(9) + ")"
12290 M9=M9+1: M$(M9)="Block offset Dev1 (" + V$(10) + ")"

12390 M9=M9+1: M$(M9)="Main Menu"
12400 HOME:?:?: M0$="Select a setting to modify:": GOSUB 40002: IF M=M9 THEN M=1: RETURN
12410 HOME: PRINT "New value for:" : ? : ?M$(M) : ?
12450 IF M<>5 THEN 12500
12460 ?"-> ";: GET V$(5): ?: V(5)=ASC(V$(5)): IF V(5)=27 THEN HOME:?"Set menu key to Esc";:GOSUB 57000: IF NOT Y THEN 12900
12470 IF V$(5)>="a" AND V$(5)<="z" THEN V(5)=V(5)-32
12475 IF KM>127 THEN V(5)=V(5)+128
12480 GOTO 12600

12500 ?"("; MN(M); " to "; MX(M); ") ";
12510 INPUT "";V$(M)
12520 IF V$(M)="" THEN 12900

12530 V(M)=VAL(V$(M))
12540 IF V(M)<MN(M) OR V(M)>MX(M) THEN C$="Out of range": GOSUB 60600: GOTO 12900

12550 IF M=6 OR M=8 THEN V(M)=INT(V(M)*10 + 0.5) : REM these are tenths of seconds

12600 REM * write new value for setting M to EEPROM
12610 POKE MSLOT,192+SLOT: Z=PEEK(CFFF): Z=PEEK(BASE)
12620 Z=PEEK(C000+128+16*SLOT+3) : REM enable writes to EEPROM

12630 V=V(M)
12640 IF M<>9 AND M<>10 THEN 12700
   REM take care of the 3-byte offset parameters
12650 V2=INT(V/65536): V=V-65536*V2
12655 V1=INT(V/256): V=V-256*V1
12660 POKE C800+OFF(M)+2,V2
12665 FOR Z=1 TO 10: IF PEEK(C800+OFF(M)+2)=V2 THEN OK=1: Z=100: NEXT: GOTO 12675
12670 NEXT: OK=0: GOTO 12800
12675 POKE C800+OFF(M)+1,V1
12680 FOR Z=1 TO 10: IF PEEK(C800+OFF(M)+1)=V1 THEN OK=1: Z=100: NEXT: GOTO 12700
12685 NEXT: OK=0: GOTO 12800

   REM stash a single-byte value in EEPROM and wait for it to stick
12700 POKE C800+OFF(M),V
12720 FOR Z=1 TO 10: IF PEEK(C800+OFF(M))=V THEN OK=1: Z=100: NEXT: GOTO 12800
12730 NEXT: OK=0

12800 Z=PEEK(C000+128+16*SLOT+4) : REM disable writes to EEPROM
12810 IF NOT OK THEN C$="Couldn't set value: check WP jumper": GOSUB 60600
12900 GOTO 12100


13999 :
14000 REM * Change cards
14010 IF CF% < 2 THEN C$ = "No other CFFA card found": GOSUB 60600: RETURN 
14020 CC% = CC% + 1
14030 IF CC% > CF% THEN CC% = 1
14040 SLOT = CF%(CC%)
14050 RETURN 
14090 :
19000 REM * Quit
19010 TEXT : HOME : PRINT "Bye!": END 
19999 :
20000 REM * Locate all CFFA cards
20010 REM CF% = number of cards, CF%(1) = first card
20020 CF% = 0
20030 FOR S = 1 TO 7
20040 BASE = C000 + S * 256
20050 IF PEEK (BASE + 246) = ASC ("C") AND PEEK (BASE + 247) = ASC ("F") AND PEEK (BASE + 248) = ASC ("F") AND PEEK (BASE + 249) = ASC ("A") THEN CF% = CF% + 1:CF%(CF%) = S
20060 NEXT 
20090 RETURN 

28999:
29000 REM * Select firmware image file
29010 ?D$;"BLOAD CFFA.FIRMWARE,TDIR,A$1000,L$1000"
29050 M9=0
29060 P=4096
   REM beginning of directory block
29100 O=4: N=13
   REM try next dir entry
29110 O=O+39: N=N-1: IF N>0 THEN 29200
   REM try next block, if any
29120 IF PEEK(P+2)=0 AND PEEK(P+3)=0 THEN 29500
29130 P=P+512: N=13: O=4

   REM directory entry is at P+O
   REM look for seedling files (storage type = 2)
29200 IF INT(PEEK(P+O)/16)<>2 THEN 29110
   REM look for filetype $00 or BIN
29210 T=PEEK(P+O+16): IF T<>0 AND T<>6 THEN 29110
   REM look for length = 4096
29220 L = PEEK(P+O+21) + 256*PEEK(P+O+22) + 65536*PEEK(P+O+23): IF L <> 4096 THEN 29110
29230 N$="": FOR I=1 TO PEEK(P+O)-32: N$=N$+CHR$(PEEK(P+O+I)): NEXT
29240 M9=M9+1: M$(M9)=N$: T%(M9)=T
29250 GOTO 29110

   REM hit end of directory
29500 IF M9=0 THEN C$="No firmware images available.": GOSUB 60600: OK=0: M=1: RETURN
29510 M9=M9+1: M$(M9)="Main Menu": M=1
29550 M0$="Choose a firmware file:" : GOSUB 40002
29560 IF M=M9 THEN OK=0: M=1: RETURN

29600 ?D$;"BLOAD CFFA.FIRMWARE/";M$(M);",T";T%(M);",A$1000,L$1000"
29610 OK=1: RETURN

29999 :
30000 REM * Upgrade EEPROM

   REM choose firmware
30100 GOSUB 29000: IF NOT OK THEN RETURN
   REM Hard-code your firmware choice here, if you want.
   REM 30100 ?D$;"BLOAD CFFA.FIRMWARE/CFFA20EEC02.BIN,T$00,A$1000,L$1000"

30200 CC$=""
30210 GOSUB 39000: IF E=0 THEN CC$="Boot ROM matches.": GOTO 30300
30220 CC$="Boot ROM does not match."+CHR$(13)+"  Diffs=" + STR$(E) + " (1st at " + STR$(E1) + ")"

30300 CC$=CC$+CHR$(13)
30305 GOSUB 39100: EE=0
30310 FOR P=7 TO 0 STEP -1
30320 GOSUB 39110: EE=EE+E
30330 IF E THEN E0=E1 + 256*P
30340 NEXT P
30350 IF EE=0 THEN CC$=CC$+"Aux ROM matches.": GOTO 30400
30360 CC$=CC$+"Aux ROM does not match."+CHR$(13)+"  Diffs=" + STR$(EE) + " (1st at " + STR$(E0) + ")"

30400 BASE = C000 + 256 * SLOT
   REM IF $CnFA vers is 255 then it's 2.0 or greater -> get the "real" version from $C822.
30410 V = PEEK (BASE + 250) : IF V=255 THEN POKE MSLOT,SLOT: Z=PEEK(CFFF): Z=PEEK(BASE): V=PEEK(C800+34)
30420 CC$=CC$+CHR$(13)+"CFFA in slot "+STR$(SLOT)+": v"+STR$(INT(V/16))+"."+STR$(V-16*(INT(V/16)))
   REM Show version from loaded firmware image
30430 V = PEEK(4096+256*SLOT+250): IF V=255 THEN V=PEEK(4096+2048+34)
30440 CC$=CC$+CHR$(13)+"Firmware file: v" + STR$(INT(V/16))+"."+STR$(V-16*(INT(V/16)))

30500 M = 1: M9=3
30520 M$(1) = "Program boot ROM ($C" + STR$(SLOT) + "xx)"
30530 M$(2) = "Program Aux ROM ($C800, 2K)
30590 M$(M9) = "Main Menu"

30700 HOME : PRINT CC$
30710 PRINT : PRINT 
30720 GOSUB 40000
30730 IF M=M9 THEN M=1:RETURN
30750 HOME : ON M GOSUB 31000,32000
  REM Clear out the screen hole so the card automatically re-initializes itself
  REM (set $478+SLOT to anything other than $A5)
30760 POKE 1144+SLOT,0
30770 GOTO 30200
30999 :

31000 REM * Program boot ROM ($Cnxx, 256 bytes)
31050 IF SLOT = 3 THEN  POKE 49163,0: REM HIT SOFTSWITCH TO TURN OFF SLOT 3 80 COL. ROM
31100 GOSUB 32500: GOSUB 32600
31110 IF E=0 THEN PRINT "Successful.":GOTO 31200
31120 ?"Failed at offset ";E1: ?" (working down from 255)": ?"Check write-protect jumper."
31200 GOSUB 61500
31210 RETURN
31999 :

32000 REM * Program Aux ROM ($C800, 2048 bytes)
32010 GOSUB 32500
32100 FOR P=7 TO 0 STEP -1
32110 GOSUB 32700
32120 IF E>0 THEN PF=P: P=-1:NEXT P:GOTO 32150
32130 NEXT P
32140 ?"Successful.": GOTO 32200

32150 ?"Failed on page ";PF;", offset ";E1
32160 ?"Check write-protect jumper."

32200 GOSUB 61500
32210 RETURN

32500 REM * Skeleton to Program one page of EEPROM.  
 REM  0 php
 REM  1 sei
 REM  2 lda $cfff
 REM  5 lda $Cn00    -- +7 = 192+SLOT
 REM  8 ldy #0       -- or ldy #$ff (+9 = START)
 REM 10 dey
 REM 11 cpy #$EF     -- need to skip $CFEF (on page 7)
 REM 13 beq @dey     -- only branch for page $CF (+14 = SKIP)
 REM 15 lda $1z00,y
 REM 18 ldx #0
 REM 20 sta $Cz00,Y
 REM 23 cmp $Cz00,Y
 REM 26 beq @tya
 REM 28 dex
 REM 29 bne @cmp
 REM 31 inc 0
 REM 33 sty 1
 REM 35 bne @plp
 REM 37 tya
 REM 38 bne @dey
 REM 40 plp
 REM 41 rts
32510 S=768: POKE S,8: POKE S+1,120: POKE S+2,173: POKE S+3,255: POKE S+4,207: POKE S+5,173: POKE S+6,0: POKE S+7,192+SLOT
32520 POKE S+8,160: POKE S+10,136: POKE S+11,192: POKE S+12,239: POKE S+13,240: POKE S+15,185: POKE S+16,0 
32530 POKE S+18,162: POKE S+19,0: POKE S+20,153: POKE S+21,0: POKE S+23,217: POKE S+24,0: POKE S+26,240: POKE S+27,9
32540 POKE S+28,202: POKE S+29,208: POKE S+30,248: POKE S+31,230: POKE S+32,0: POKE S+33,132: POKE S+34,1
32550 POKE S+35,208: POKE S+36,3: POKE S+37,152: POKE S+38,208: POKE S+39,226: POKE S+40,40: POKE S+41,96
32560 RETURN

32600 REM * Write boot ROM for SLOT - return E=1 if error, E1=index of failure (working backwards from end)
32610 SKIP=0: START=0
32620 POKE S+9,START: POKE S+14,SKIP: POKE S+17,16+SLOT: POKE S+22,192+SLOT: POKE S+25,192+SLOT
32630 POKE 0,0: POKE 1,1: CALL S
32640 E=PEEK(0): E1=PEEK(1): IF E=0 AND E1=0 THEN E=256
32650 RETURN

32700 REM * Write page P of Aux ROM - return E=1 if error, E1=index of failure (working backwards from end)
32710 SKIP=0: START=0: IF P=7 THEN SKIP=251: START=255: REM branch offset after CPY #$EF, countdown to skip $CFFF
32720 POKE S+9,START: POKE S+14,SKIP: POKE S+17,16+8+P: POKE S+22,192+8+P: POKE S+25,192+8+P
32730 POKE 0,0: POKE 1,1: CALL S
32740 E=PEEK(0): E1=PEEK(1): IF E=0 AND E1=0 THEN E=256
32750 RETURN


39000 REM * Verify Boot ROM -- compare $Cnxx to $1nxx, returning E=error count, E1=offset of lowest difference.
  REM    ldy #0
  REM    dey
  REM    lda $Cn00,y
  REM    cmp $1n00,y
  REM    beq +4
  REM    inc 0
  REM    sty 1
  REM    tya
  REM    bne @dey
  REM    rts
39010 S=768: POKE S,160: POKE S+1,0: POKE S+2,136: POKE S+3,185: POKE S+4,0: POKE S+5,192+SLOT: POKE S+6,217: POKE S+7,0: POKE S+8,16+SLOT
39020 POKE S+9,240: POKE S+10,4:POKE S+11,230:POKE S+12,0:POKE S+13,132:POKE S+14,1:POKE S+15,152:POKE S+16,208:POKE S+17,240:POKE S+18,96
39030 POKE 0,0: POKE 1,1: CALL S
39040 E=PEEK(0): E1=PEEK(1): IF E=0 AND E1=0 THEN E=256
39090 RETURN

39100 REM * Set up verify-page (P=0 to 7) of Aux ROM -- compare $Czxx to $1zxx, returning E=error count, E1=offset of lowest difference.
 REM  0 php
 REM  1 sei
 REM  2 lda $cfff
 REM  5 lda $Cn00  -- +7 = 192+SLOT
 REM  8 ldy #0     -- or ldy #$ff (+9 = START)
 REM 10 dey
 REM 11 cpy #$EF   -- need to skip $CFEF
 REM 13 beq @dey   -- only branch for page $CF ($F0 FB, or $F0 00 to fall thru) (+14 = SKIP)
 REM 15 lda $Cz00,y
 REM 18 cmp $1z00,y
 REM 21 beq @tya
 REM 23 inc 0
 REM 25 sty 1
 REM 27 tya
 REM 28 bne @dey
 REM 30 plp
 REM 31 rts
39101 S=768: POKE S,8: POKE S+1,120: POKE S+2,173: POKE S+3,255: POKE S+4,207: POKE S+5,173: POKE S+6,0: POKE S+7,192+SLOT
39102 POKE S+8,160: POKE S+10,136: POKE S+11,192: POKE S+12,239: POKE S+13,240: POKE S+15,185: POKE S+16,0 
39103 POKE S+18,217: POKE S+19,0: POKE S+21,240: POKE S+22,4: POKE S+23,230: POKE S+24,0
39104 POKE S+25,132: POKE S+26,1: POKE S+27,152: POKE S+28,208: POKE S+29,236: POKE S+30,40: POKE S+31,96
39105 RETURN
39110 REM * Verify page P of Aux ROM
39120 SKIP=0: START=0: IF P=7 THEN SKIP=251: START=255: REM branch offset after CPY #$EF, countdown to skip $CFFF
39130 POKE S+9,START: POKE S+14,SKIP: POKE S+17,192+8+P: POKE S+20,16+8+P
39170 POKE 0,0: POKE 1,1: CALL S
39180 E=PEEK(0): E1=PEEK(1): IF E=0 AND E1=0 THEN E=256
39190 RETURN
39199 :

39999 :
40000 REM  * MENU (M$(), M9, M)
      REM M$()=choices, M9=number of choices, M=current choice
40001 M0$ = "Select one of the following:"
40002 REM * Menu w/ msg M0$
40005 HTAB 1
40010 PRINT M0$: PRINT 
40020 V = PEEK (37) + 1
40021 IF M9 > 10 THEN 40030
40022 VTAB 22:C$ = "Use arrows, numbers, or letters to": GOSUB 60000:C$ = "select; then press RETURN": GOSUB 60000: VTAB V
40030 FOR I9 = 1 TO M9: PRINT SPC( 1 + (I9 < 10));I9;".  ";M$(I9): NEXT 
40050 REM  * MAIN LOOP FOR MENU
40052 IF M = 0 THEN M = M9
40053 IF M = M9 + 1 THEN M = 1
40055 HTAB 6: INVERSE : VTAB V - 1 + M: PRINT " ";M$(M);" ";: NORMAL 
40060 GOSUB 41000: HTAB 6: VTAB V - 1 + M: PRINT " ";M$(M);" ";
40070 IF A$ = CHR$ (27) THEN M = M9: GOTO 40050
40080 IF A$ = CHR$ (13) THEN VTAB V - 1 + M: HTAB 1: RETURN
40090 IF A$ = CHR$ (8) OR A$ = CHR$ (11) THEN M = M - 1: GOTO 40050
40100 IF A$ = CHR$ (21) OR A$ = CHR$ (10) THEN M = M + 1: GOTO 40050
40102 IF VAL (A$) AND ( VAL (A$) > M9) THEN 40110
40105 IF VAL (A$) THEN M = VAL (A$): GOTO 40050
40107 GOSUB 42000: IF OK THEN 40050
40110 GOSUB 57900: GOTO 40050
40120 :
41000 WAIT - 16384,128: GET A$: RETURN 
41080 :
42000 REM * ADVANCE TO ENTRY STARTING WITH A$, IF PRESENT
42010 OK = 0
42020 IF A$ > = "a" AND A$ < = "z" THEN A$ = CHR$ ( ASC (A$) - 32)
42025 M0 = M
42030 FOR CNT = 1 TO M9
42040 M0 = M0 + 1: IF M0 > M9 THEN M0 = 1
42050 IF A$ = LEFT$ (M$(M0),1) THEN CNT = 999: NEXT :M = M0:OK = 1: RETURN 
42060 NEXT CNT: RETURN 
42070 :
49999 :
50000 REM * Initialize
50010 DIM M$(20),T%(20),V(20),V$(20),MN(20),MX(20),OFF(20)
50015 GOSUB 59900
50030 D$ = CHR$ (4)
50040 MSLOT=2040: C000=49152: C800=51200: CFFF=53247
50900 RETURN 
50910 :
57000 REM * Yes/No question; return Y=0/1
57010 PRINT "? (Y/N) ";
57020 GET A$: IF A$>="a" THEN A$=CHR$(ASC(A$)-32)
57030 IF A$ = "Y" THEN PRINT "yes":Y = 1: RETURN 
57040 IF A$ = "N" THEN PRINT "no":Y = 0: RETURN 
57050 GOSUB 57900: GOTO 57020
57060 :
57900 REM * ERROR TONE
57910 PRINT CHR$ (7);
57920 RETURN 
57930 :
59900 REM * INIT SCREEN
59910 TEXT : HOME : NORMAL : SPEED= 255: NOTRACE 
59920 REM * Install lowercase-filter
 REM $3C0: uppercaseInverseLetters: (for Apple IIe and later has $FBB3 = 6, PEEK(-1101)=6)
 REM   cld
 REM   bit $32
 REM   bmi @out
 REM $3C5: uppercaseAllLetters:  (for Apple II+)
 REM   cld
 REM   cmp #$E0
 REM   bcc @out
 REM   and #$DF
 REM out:
 REM   jmp $fdf0
 REM
59930 POKE 960,216: POKE 961,36: POKE 962,50: POKE 963,48: POKE 964,7: POKE 965,216: POKE 966,201: POKE 967,224: POKE 968,144
59931 POKE 969,2: POKE 970,41: POKE 971,223: POKE 972,76: POKE 973,240: POKE 974,253
59940 ?CHR$(4);"PR# A";960 + 5 * (PEEK(-1101) <> 6)
 REM used to use this on GS *** PRINT CHR$ (4);"PR#3": PRINT CHR$ (17);: REM * 80-col firmware, but 40-column mode (want inverse lowercase)

59950 H$ = "---------------------------------------"
59960 PRINT H$: PRINT "CFFA Utility v";VN$;: HTAB 40-LEN(DT$):?DT$
59970 PRINT H$
59980 POKE 34,4: VTAB 5
59990 RETURN 
59999 :
60000 REM  * CENTER C$
60010 HTAB 21 - LEN (C$) / 2: PRINT C$: RETURN 
60500 REM * CENTER C$ IN BOX
60510 TEXT : HOME 
60520 A$ = "****************************************":B$ = "*                                      *"
60530 PRINT A$;B$;B$;B$;A$: VTAB PEEK (37) - 3: GOSUB 60000: PRINT : PRINT : PRINT : RETURN 
60598 :
60599 REM *Error C$
60600 VTAB 9: GOSUB 60520: PRINT CHR$ (7);
60610 GOSUB 61500
60620 RETURN 
60630 :
61498 :
61499 REM * Press RETURN to continue
61500 VTAB 24: HTAB 1: PRINT "Press RETURN to continue: ";
61510 GET KK$: IF KK$ < > CHR$ (13) THEN 61500
61520 RETURN 
61999 :
62000 REM  * Print A$ w/o breaking words
62005 IF LEFT$ (A$,1) = " " THEN A$ = MID$ (A$,2): GOTO 62005
62010 IF LEN (A$) < 40 THEN PRINT A$: GOTO 62100
62020 FOR P9 = 40 TO 1 STEP - 1: IF MID$ (A$,P9,1) = " " THEN PRINT LEFT$ (A$,P9 - 1):A$ = MID$ (A$,P9 + 1): GOSUB 62100: GOTO 62000
62030 NEXT : PRINT A$: GOSUB 62100: RETURN 
62040 :
62050 REM  * NEW LINE
62100 L9 = L9 + 1: IF L9 = 18 THEN L9 = 1: GOSUB 61500: HOME 
62110 RETURN 
63999 PRINT CHR$ (4);"SAVE CFFA.UTIL"

SAVE CFFA.UTIL
PRINT "Saved as CFFA.UTIL"
